package base.thread_JUC.demo03;


import lombok.Data;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;

/**
 * MYNOTICE
 * 手写一个自旋锁,实现高并发情况下对共享资源的线程安全性保护。
 *
 * 自旋锁：是指尝试获取锁的线程不会立即阻塞，而是采用循环的方式去尝试获取锁，这样的好处是减少线程上下文切换的消耗，缺点是循环会消耗CPU。
 *
 * 自旋好处：线程循环获取并比较，直到成功拿到锁，没有成功就接着循环比较。减少线程上下文切换的开销，缺点就是耗CPU
 *
 * 通过 CAS操作完成自旋锁，t1线程先进来调用myLock方法，并持有锁5s，t2随后进来，发现当前有线程持有锁，不是null，所以只能通过
 * 自旋等待，直到t1线程释放锁后，t2线程随后抢到。
 */
public class Client2 {

    public static void main(String[] args) {
//        Demo0 demo = new Demo0();
//        Demo1 demo = new Demo1();
        Demo2 demo = new Demo2();

        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    demo.add();
                }
            },String.valueOf(i)).start();
        }

        /** MYNOTICE  注意这里用的是while循环，而不是if
         *
         * 主线程等待上面所有线程执行完毕
         */
        while (Thread.activeCount()>2) {
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+"多线程环境下 result="+demo.getNum());
    }

}

// 使用 JUC 提供的 AtomicInteger
@Data
class Demo0 {
    private AtomicInteger num = new AtomicInteger();

    public void add(){
        num.getAndIncrement();
    }
}

// 使用 ReentrantLock 锁
@Data
class Demo1 {
    private int num =0;

    private ReentrantLock lock = new ReentrantLock(false);//非公平锁
    public void add(){
        try {
            lock.lock();
            num++;
        }finally {
            lock.unlock();
        }
    }
}

// 用自定义自旋锁
@Data
class Demo2 {
    private int num;

    private SpinLock spinLock = new SpinLock();
    public void add(){
        try {
            spinLock.myLock();
            num++;
        }finally {
            spinLock.myUnlock();
        }
    }
}


/**
 * MYNOTICE
 * 自定义自旋锁
 * 将一个线程的内部操作看做一个原子操作，那么这个线程在操作过程中不允许被打断，因此将 Thread 类作为 AtomicReference 的泛型。表示 Thread 是原子的。
 * 在 myLock 和 myUnlock 之间的代码是要处于原子操作的一个整体，不允许被打断，切记 myLock 和 myUnlock 要成对使用。
 */
class SpinLock {
    /**
     * 原子引用线程
     * 好处：线程对外可见性(AtomicReference<V>的 private volatile V value;对泛型类可见)，不可见的话，
     * 自旋时会不读取主存中内容，导致while代码块进入死循环
      */
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock(){
        /**
         * new AtomicReference<Thread>(): -> value = null,还未有线程。
         */
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName()+" come in");
        /**
         * 每次循环都去 compareAndSet()，不可抽取成结果,将结果while(!result)
         */
        while(!atomicReference.compareAndSet(null, thread)){
            // 如果比较结果是 false 说明非当前线程操作，进入空循环等待
        }
//        atomicReference.getAndSet(thread);
    }

    public void myUnlock(){
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(thread.getName()+" invoked myUnlock");
    }

}
